﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Collections.Specialized;
using System.Xml;
using System.Diagnostics;
using System.Windows.Forms;
using System.Xml.Schema;

namespace WikiTrailCreator.WikiGeneration
{
    class WikiSession
    {
        private CookieContainer cookieContainer = new CookieContainer();
        private string serverURL = null;

        public WikiSession(String serverURL)
        {
            this.serverURL = serverURL;
        }

        public bool Login(string userName, string password)
        {
            bool bLoginFailed = false;

            // Get the 'login' web page...
            XmlDocument xmlDoc = GetPage(serverURL + "index.php?title=Special:UserLogin");
            if (xmlDoc == null)
                return false;

            // Get important data from the page...
            string wpLoginattempt = GetElementValueBy(xmlDoc, "input", "name", "wpLoginattempt");
            
            // Make sure that we have all required data...
            if (wpLoginattempt == null ||
                !HasCookie("session")
                )
                return false;

            // Prepare the login data...
            ASCIIEncoding encoding = new ASCIIEncoding();
            string loginPostData = "wpName=" + userName;
            loginPostData += "&wpPassword=" + password;
            loginPostData += "&wpLoginattempt=" + wpLoginattempt;
            byte[] loginPostDataEncoded = encoding.GetBytes(loginPostData);

            // Prepare the post request that will log us in...
            HttpWebRequest loginToServerRequest = (HttpWebRequest)WebRequest.Create(serverURL + "index.php?title=Special:UserLogin");
            loginToServerRequest.CookieContainer = cookieContainer;
            loginToServerRequest.Method = "POST";
            loginToServerRequest.ContentType = "application/x-www-form-urlencoded";
            loginToServerRequest.ContentLength = loginPostDataEncoded.Length;

            // Write the data to the login request object...
            Stream loginRequestStream = loginToServerRequest.GetRequestStream();
            loginRequestStream.Write(loginPostDataEncoded, 0, loginPostDataEncoded.Length);
            loginRequestStream.Close();

            // Send off the login request...
            HttpWebResponse loginToServerResponse = (HttpWebResponse)loginToServerRequest.GetResponse();
            bLoginFailed = (loginToServerResponse.StatusCode != HttpStatusCode.OK);
            loginToServerResponse.Close();

            // Make sure that we have the cookies for our logged in session
            if (!HasCookie("UserName") || !HasCookie("UserID"))
                return false;

            return (bLoginFailed == false);
        }

        public bool UploadImage(string destFileName, string filePath, string description)
        {
            // Get the 'create a page' web page...
            XmlDocument xmlDoc = GetPage(serverURL + "index.php?title=Special:Upload&wpDestFile=" + destFileName);
            if (xmlDoc == null)
                return false;

            // Find the important elements from the page and read their values...
            string wpSourceType = GetElementValueBy(xmlDoc, "input", "name", "wpSourceType");
            string wpUpload = GetElementValueBy(xmlDoc, "input", "name", "wpUpload");
            string wpDestFileWarningAck = GetElementValueBy(xmlDoc, "input", "name", "wpDestFileWarningAck");
            string wpForReUpload = GetElementValueBy(xmlDoc, "input", "name", "wpForReUpload");

            // If we don't have all the page data, fail...
            if (wpSourceType == null ||
                wpUpload == null ||
                wpDestFileWarningAck == null ||
                wpForReUpload == null
                )
                return false;

            // Prepare data for file upload...
            NameValueCollection nvc = new NameValueCollection();
            nvc.Add("wpDestFile", destFileName);
            nvc.Add("wpSourceType", wpSourceType);
            nvc.Add("wpUploadDescription", description);
            nvc.Add("wpWatchthis", "1");
            nvc.Add("wpIgnoreWarning", "1");
            nvc.Add("wpUpload", wpUpload);
            nvc.Add("wpDestFileWarningAck", wpDestFileWarningAck);
            nvc.Add("wpForReUpload", wpForReUpload);

            // Request file creation...
            XmlDocument xmlDoc2 = PostFormDataToRemoteUrl(serverURL + "index.php?title=Special:Upload", "wpUploadFile", new string[] { filePath }, nvc);

            return true;
        }

        public bool CreatePage(string pageTitle, string pageText)
        {
            // Get the 'create a page' web page...
            XmlDocument xmlDoc = GetPage(serverURL + "index.php?title=" + pageTitle + "&action=edit");
            if (xmlDoc == null)
                return false;

            // Find the important elements from the page and read their values...
            string wpStartTime = GetElementValueBy(xmlDoc, "input", "name", "wpStarttime");
            string wpEditTime = GetElementValueBy(xmlDoc, "input", "name", "wpEdittime");
            string wpAutoSummary = GetElementValueBy(xmlDoc, "input", "name", "wpAutoSummary");
            string wpEditToken = GetElementValueBy(xmlDoc, "input", "name", "wpEditToken");
            string wpScrolltop = GetElementValueBy(xmlDoc, "input", "name", "wpScrolltop");
            string wpSave = GetElementValueBy(xmlDoc, "input", "name", "wpScrolltop");
            string wpSection = GetElementValueBy(xmlDoc, "input", "name", "wpScrolltop");

            // If we don't have all the page data, fail...
            if (wpStartTime == null ||
                wpEditTime == null ||
                wpAutoSummary == null ||
                wpEditToken == null ||
                wpScrolltop == null ||
                wpSave == null ||
                wpSection == null
                )
                return false;
            
            // Prepare 'create page' data for file upload...
            NameValueCollection nvc = new NameValueCollection();
            nvc.Add("wpStarttime", wpStartTime);
            nvc.Add("wpEdittime", wpEditTime);
            nvc.Add("wpScrolltop", wpScrolltop);
            nvc.Add("wpAutoSummary", wpAutoSummary);
            nvc.Add("wpSummary", "This page was generated automatically by Wiki Trail Creator.");
            nvc.Add("wpWatchthis", "1");
            nvc.Add("wpMinoredit", "0");
            nvc.Add("wpSave", wpSave);
            nvc.Add("wpTextbox1", pageText);
            nvc.Add("wpSection", wpSection);
            nvc.Add("wpEditToken", wpEditToken);

            // Create the page using a post command...
            string creationPageUrl = serverURL + "index.php?title=" + pageTitle + "&action=submit";
            XmlDocument xmlDoc2 = PostFormDataToRemoteUrl(creationPageUrl, null, null, nvc);

            return true;
        }

        private string GetElementValueBy(XmlDocument xmlDoc, string tagName, string attrName, string value)
        {
            XmlNodeList inputElements = xmlDoc.GetElementsByTagName(tagName);
            XmlNode wpEditTokenElement = null;
            foreach (XmlNode xmlNode in inputElements)
            {
                XmlAttribute nameAttr = xmlNode.Attributes[attrName];

                if (nameAttr != null && nameAttr.Value == value)
                {
                    wpEditTokenElement = xmlNode;
                    break;
                }
            }

            string nodesValue = null;
            if (wpEditTokenElement != null)
            {
                XmlAttribute valueAttr = wpEditTokenElement.Attributes["value"];
                if (valueAttr != null)
                    nodesValue = valueAttr.Value;
            }

            return nodesValue;
        }

        private XmlDocument GetPage(string url)
        {
            HttpWebResponse pageResponse = null;

            try
            {
                // Get the page...
                HttpWebRequest pageRequest = (HttpWebRequest)WebRequest.Create(url);
                pageRequest.CookieContainer = cookieContainer;
                pageRequest.UserAgent = "WikiTrailCreator";
                pageResponse = (HttpWebResponse)pageRequest.GetResponse();

                // If we couldn't even get the page, fail...
                if (pageResponse.StatusCode != HttpStatusCode.OK)
                {
                    return null;
                }

                // Load the reponse as an XmlDocument...
                StreamReader rdr = new StreamReader(pageResponse.GetResponseStream());
                string xmlString = rdr.ReadToEnd();
                xmlString = xmlString.Replace("http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd", "xhtml1-transitional.dtd"); 
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.LoadXml(xmlString);
                return xmlDoc;
            }
            finally
            {
                if (pageResponse != null)
                    pageResponse.Close();
            }
        }

        private XmlDocument PostFormDataToRemoteUrl(string url, string nameOfFileInputBox, string[] files, NameValueCollection nvc)
        {
            string boundary = "----------------------------" + DateTime.Now.Ticks.ToString("x");

            HttpWebRequest httpWebRequest2 = (HttpWebRequest)WebRequest.Create(url);
            httpWebRequest2.ContentType = "multipart/form-data; boundary=" + boundary;
            httpWebRequest2.Method = "POST";
            httpWebRequest2.CookieContainer = cookieContainer;
            httpWebRequest2.KeepAlive = true;
            httpWebRequest2.Credentials = System.Net.CredentialCache.DefaultCredentials;
            httpWebRequest2.UserAgent = "WikiTrailCreator";

            Stream memStream = new System.IO.MemoryStream();

            byte[] boundarybytes = System.Text.Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n");

            string formdataTemplate = "\r\n--" + boundary + "\r\nContent-Disposition: form-data; name=\"{0}\";\r\n\r\n{1}";

            foreach (string key in nvc.Keys)
            {
                string formitem = string.Format(formdataTemplate, key, nvc[key]);
                byte[] formitembytes = System.Text.Encoding.UTF8.GetBytes(formitem);
                memStream.Write(formitembytes, 0, formitembytes.Length);
            }

            memStream.Write(boundarybytes, 0, boundarybytes.Length);

            if (files != null)
            {
                string headerTemplate = "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\n Content-Type: application/octet-stream\r\n\r\n";

                for (int i = 0; i < files.Length; i++)
                {
                    string header = string.Format(headerTemplate, nameOfFileInputBox, files[i]);

                    byte[] headerbytes = System.Text.Encoding.UTF8.GetBytes(header);

                    memStream.Write(headerbytes, 0, headerbytes.Length);

                    FileStream fileStream = new FileStream(files[i], FileMode.Open,
                    FileAccess.Read);
                    byte[] buffer = new byte[1024];

                    int bytesRead = 0;

                    while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
                    {
                        memStream.Write(buffer, 0, bytesRead);

                    }

                    memStream.Write(boundarybytes, 0, boundarybytes.Length);

                    fileStream.Close();
                }
            }

            httpWebRequest2.ContentLength = memStream.Length;

            Stream requestStream = httpWebRequest2.GetRequestStream();

            memStream.Position = 0;
            byte[] tempBuffer = new byte[memStream.Length];
            memStream.Read(tempBuffer, 0, tempBuffer.Length);
            memStream.Close();
            requestStream.Write(tempBuffer, 0, tempBuffer.Length);
            requestStream.Close();

            XmlDocument xmlResponse = null;
            try
            {
                WebResponse webResponse2 = httpWebRequest2.GetResponse();

                // Load the reponse as an XmlDocument
                StreamReader rdr = new StreamReader(webResponse2.GetResponseStream());
                string xmlString = rdr.ReadToEnd();
                xmlString = xmlString.Replace("http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd", "xhtml1-transitional.dtd");
                xmlResponse = new XmlDocument();
                xmlResponse.LoadXml(xmlString);

                webResponse2.Close();
                httpWebRequest2 = null;
                webResponse2 = null;
            }
            catch (WebException e)
            {
                Trace.WriteLine(e);
            }

            return xmlResponse;
        }

        private bool HasCookie(string cookieName)
        {
            foreach (Cookie cookie in cookieContainer.GetCookies(new Uri(serverURL)))
            {
                if (cookie.Name.Contains(cookieName))
                {
                    return true;
                }
            }

            return false;
        }
    }
}
